<?php
require_once ROOT_PATH.'boot/req_inc.php';

class PDODB implements IDB,IRequestTool{
	private $conf;
	private $dbconnsel_ok;
	private $pdo;
	private $sth;
	private $dbid;
	private $sel_db;		//当前选择主数据库
	private $cache_lifetime;

	private $col_info_prefix='pdodb/col_info/';	//table col info cache key prefix
	private $nest_trans=false;					//nested transaction support
	//end private

	public 	$options;
	/**
	 * @var integer  get_*返回数组row的记录数组key是数字，还是字符串，默认仅为字符串。
	 * <br> -1：仅为数字，
	 * <br>  0：仅为字符串，
	 * <br>  1：同时包含数字和字符串；
	 */
	public	$row_key_is=self::KEY_TEXT;
	const   KEY_DIGIT=-1; //for $row_key_is
	const   KEY_TEXT=0;
	const   KEY_BOTH=1;

	public  $adapter;

	public  $dbtype;
	public  $QUOTE_CHAR = '`';

	/**
	 * @var string 上次执行的sql
	 */
	public	$last_query;
	/**
	 * @var array 上次执行的数值
	 */
	public	$last_values;

	private $cache_valid=false;

	private $chief_table = array('shop_cart', 'user_users', 'order_info', 'order_goods');

	/**
	 * @var array $dbconf 数据库参数，包括host,user,pwd,name,type,port,charset 。 oracle 还包括:tns
	 */
	function __construct(array $dbconf){
		$this->options = array(
			PDO::ATTR_ERRMODE			=> PDO::ERRMODE_EXCEPTION,
			PDO::ATTR_ORACLE_NULLS		=> PDO::NULL_NATURAL,
			PDO::ATTR_STRINGIFY_FETCHES	=> false
			);
		$this->set_conf($dbconf);
		$this->dbconnsel_ok =extension_loaded('dbconnsel');
		//获取配置中的缓存时间
		$this->cache_lifetime = $GLOBALS['context']->get_app_conf('cache_lifetime') ? $GLOBALS['context']->get_app_conf('cache_lifetime') : 300;
	}
	function set_conf(array $conf){
		$this->conf=$conf;
		$this->dbtype=isset($this->conf['type'])? $this->conf['type']:'mysql';
		if($this->dbtype=='mysql'){
			require_once dirname(__FILE__).DIRECTORY_SEPARATOR.'PDOAdapterMysql.class.php' ;
			$this->adapter=new PDOAdapterMysql();
		}else if($this->dbtype=='oracle' || $this->dbtype=='oci'){
			$this->options[PDO::ATTR_CASE] = PDO::CASE_LOWER;
			$this->dbtype='oci';
			require_once dirname(__FILE__).DIRECTORY_SEPARATOR.'PDOAdapterOci.class.php';
			$this->adapter=new PDOAdapterOci();
		}else{
			$GLOBALS['context']->put_error(10000,lang('db_err_invalid_type').$this->dbtype);
			throw new Exception(lang('db_err_invalid_type') . $this->dbtype);
		}
		$this->adapter->db=$this;
		$this->QUOTE_CHAR= $this->adapter->QUOTE_CHAR;
		$this->nest_trans= $this->adapter->nest_trans;
		$this->dbid=NULL;
		$this->select_db($this->conf['name'],isset($this->conf['region']) ?  $this->conf['region'] : NULL);
	}
	function alias($alias){
		return $this->adapter->alias($alias);
	}
	function get_pdo($readonly=true){
		if(! $this->sel_db) throw new Exception(lang('db_err_dbname_null'));
		$host=$user=$pwd=$port=NULL;
		$this->get_host($readonly,$this->sel_db, $host, $user, $pwd, $port);
		//echo "{$host};{$this->sel_db}@{$user}@{$pwd}@{$port}";
		$dbid=crc32("{$host};{$this->sel_db}@{$user}@{$pwd}@{$port}");
		if($this->pdo && $dbid===$this->dbid)	return $this->pdo;

		$this->dbid=$dbid;
		$conf=$this->conf;
		$conf['host']= $host;
		$conf['name'] = $this->sel_db;
		$conf['port'] = $port;
		$this->cache_valid=false;	//clear info cache
		$dsn=$this->adapter->get_dsn($conf);
		try{
			$this->pdo = @new PDO($dsn, $user, $pwd, $this->options);
			$this->adapter->init_pdo($this,$this->pdo);
		}catch(PDOException $e){
			$n_db_conf = array('read_host'=>array(), 'write_host'=>array());
			$nc_db_conf = array('read_host'=>array(), 'write_host'=>array());
			$o_db_conf = $GLOBALS['context']->mutil_db_cache->get('mutil_db_conf');
			if (isset($o_db_conf['read_host']) && !empty($o_db_conf['read_host'])) {
				foreach($o_db_conf['read_host'] as $k=>$v){
					if($v['host'] != $host){
						array_push($n_db_conf['read_host'], $v);
					}else{
						array_push($nc_db_conf['read_host'], $v);
					}
				}
			}
			if (isset($o_db_conf['write_host']) && !empty($o_db_conf['write_host'])) {
				foreach($o_db_conf['write_host'] as $k=>$v){
					if($v['host'] != $host){
						array_push($n_db_conf['write_host'], $v);
					}else{
						array_push($nc_db_conf['write_host'], $v);
					}
				}
			}
			$GLOBALS['context']->mutil_db_cache->set('mutil_db_conf', $n_db_conf);

			if(!empty($nc_db_conf['read_host']) || !empty($nc_db_conf['write_host'])){
				$app_conf_file = ROOT_PATH.$GLOBALS['context']->app_name.'/cache/mutil_db_conn_failure.php';
				file_put_contents($app_conf_file, "<?php\r\n\$db_conf_failure=".var_export($nc_db_conf, true).";");

				$dba_mail = $GLOBALS['context']->get_app_conf('dba_mail');
				if($dba_mail){
					exec('echo "Database conntion failure!Please check:'.print_r($nc_db_conf, true).'"|mail -s "Database conntion" '.$dba_mail);
				}
			}

			$this->get_pdo($readonly);
		}
		return $this->pdo;
	}
	/**
	 * 得到$readonly,$db对应的数据库服务器，PDODB通过数据库名称，读写参数找到实际数据库服务器。
	 * @param bool $readonly	是否仅读
	 * @param string $db		数据库名称
	 * @param string $host		数据库服务器
	 * @param string $user		数据库用户名
	 * @param string $pwd		数据库密码
	 */
	protected function get_host($readonly,$db,& $host,& $user,& $pwd ,&$port){
		if($this->dbconnsel_ok ){
     		$result=dbconn_select($readonly,$db);
     		if($result){
				$host= $result['connect'] ;
				$user= $result['user'];
     			$pwd = $result['password'];
     			$port = $result['port'];
        	}
		}else{
				$host= $this->conf['host'] ;
				$user= $this->conf['user'];
     			$pwd = $this->conf['pwd'];
     			if(isset($this->conf['port']) && !empty($this->conf['port'])) {
     				$port = $this->conf['port'];
     			}
		}
	}
	/**
	 * 得到$table,$region对应的数据库名称，PDODB通过返回的数据库名称找到实际数据库服务器，实现分库分表时需要重载此函数。
	 * @param $table			表名称
	 * @param $region_value		分库分表关键字
	 */
	protected function get_db($table,$region_value=NULL){
	 	return $this->conf['name'];
	}
	/**
	 * 选择当前主数据库
	 * @param string $tname_or_sel_db 	需要选择对应的表名或数据库名称，如果$region==NULL，为数据库名称，否则为表名
	 * @param string $region  			如果从表名选择数据库，对应的分库分表的关键值，默认不使用
	 */
	function select_db($tname_or_sel_db,$region=NULL){
		if($region) $this->sel_db=$this->get_db($tname_or_sel_db,$region);
		else $this->sel_db=$tname_or_sel_db;

		return $this->sel_db;
	}
	/**
	 * 得到表对应的包括数据库名的完整表名。
	 * @param string $table			表名
	 * @param string $region  		对应的表的分库关键值，无效
	 * @return string 返回包括数据库名的完整表名。
	 */
	function table($table,$region=NULL){
		$table=trim($table);
		if($table[0]=='(') return $table;		//if sql clause () ,return

		if($this->dbtype=='oci'){
		   $name = $this->conf['user'];
		}else{
			$name=$this->sel_db;
			//if($region===NULL) $name=$this->sel_db;
			//else $name=$this->get_db($table,$region);
		}

		return ($name ? $this->quote($name).'.' : '')  . $this->quote($table);
	}
	public	$next_no_execute=false;
	/**
	 * 设置下一次不实际执行sql语句，调用执行方法后自动为false。
	 */
	function set_next_no_execute(){
		$this->next_no_execute=true;
	}
	/**handle origin db sql**/
	/**
	 * 准备sql语句
	 * @param string $sql sql语句，建议采用占位符或?，防止sql注入，提高执行速度
	 * @param boolean|NULL $ro_hint readonly_hint sql语句只读提示，NULL：忽略此提示，分析sql语句是否查询，true：只读，false：可写。
	 */
	function prepare($sql,$ro_hint=NULL){
    	if($ro_hint===NULL){
    		$sql=trim($sql);
    		$readonly=$this->adapter->get_readonly($sql);
    	}else $readonly= $ro_hint=== true ? true :false;

		$this->last_query =& $sql;
		if($this->next_no_execute) return true;


		try {
			$this->get_pdo($readonly);
		} catch (Exception $e) {
			$err=lang('db_err_connect').var_export($this->conf, true).$e->getMessage();
			if(isset($GLOBALS['context'])) $GLOBALS['context']->log_error($err);
			//throw new Exception($err);
		}
		try {
			$this->sth = $this->pdo->prepare($sql);
		} catch (Exception $e) {
			$err=lang('db_err_prepare')."[{$e->getCode()}],{$e->getMessage()}";
			$GLOBALS['context']->log_error($err.','.$this->last_query);
			//throw new Exception($err);
		}
		return $this->sth;
	}
	/**
	 * 执行sql语句
	 * @param array $values  占位符或?对应的值
	 * @param array $is_bind_param  是否采用bindParam方式，存储过程需要设定为true。
	 */
	function execute(array $values=array(),$is_bind_param=false){
		$this->last_values = & $values;
		if($this->next_no_execute){
			$this->next_no_execute=false;
			return true;
		}
		if(! $this->sth) return false;
		if(isset($GLOBALS['context']) && $GLOBALS['context']->is_debug()){
			if(DEBUG === false){
				$log_level = 0;
			} else if(DEBUG === true) {
				$log_level = 1;
			} else {
				$log_level = DEBUG;
			}
			$wsql = true;
			$this->last_query = trim($this->last_query);
			$is_show = strripos($this->last_query, 'show');
			$is_select = strripos($this->last_query, 'select');
			switch($log_level){
				case 1:
					$wsql = true;
					break;
				case 2:
					if($is_select === 0 || $is_show === 0){
						$wsql = false;
					}
					break;
				case 3:
					if($is_select !== 0 && $is_show !== 0){
						$q_len = strlen($this->last_query);
						$is_insert = strripos($this->last_query, 'insert');
						$is_update = strripos($this->last_query, 'update');
						$is_delete = strripos($this->last_query, 'delete');
						if($is_update === 0){
							$set_pos = strripos($this->last_query, ' set ');
							$tables = substr($this->last_query, 6, -($q_len-$set_pos));
							foreach($this->chief_table as $r){
								if(strripos($tables, $r) !== false){
									$wsql = true;
									break;
								}else{
									$wsql = false;
								}
							}
						}else if($is_insert === 0){
							$set_pos = strripos($this->last_query, ' values');
							$tables = substr($this->last_query, 6, -($q_len-$set_pos));
							foreach($this->chief_table as $r){
								if(strripos($tables, $r) !== false){
									$wsql = true;
									break;
								}else{
									$wsql = false;
								}
							}
						}else if($is_delete === 0){
							$wsql = true;
						}else{
							$wsql = false;
						}
					}else{
						$wsql = false;
					}
					break;
				default:
					$wsql = false;
					break;
			}
			if($wsql){
		 		$GLOBALS['context']->log_debug('query sql：'.$this->last_query."\n".print_r($values,true));
			}
		 	reset($values);
		}
		try {
			$this->sth->closeCursor();
			if(count($values)>0){
				foreach($values as $key=>$val){
					if(is_bool($val))	$bmode=PDO::PARAM_BOOL;
					elseif(is_int($val) || is_float($val)) $bmode=PDO::PARAM_INT;
					elseif(is_string($val)) $bmode=PDO::PARAM_STR;
					elseif(is_null($val)) $bmode=PDO::PARAM_NULL;
					else{
						//pump first value
						if( is_object( $val ) ) $val=get_object_vars($val);
						if ( is_array ( $val ) ){
							reset($val);
							$val = count ( $val ) > 0  ? $val[key($val)] : NULL;
						}
						if(isset($GLOBALS['context']))
							$GLOBALS['context']->log_error('values item is array or object,query sql：'.$this->last_query."\n".print_r($values,true));

						if(is_bool($val))	$bmode=PDO::PARAM_BOOL;
						elseif(is_int($val) || is_float($val)) $bmode=PDO::PARAM_INT;
						elseif(is_string($val)) $bmode=PDO::PARAM_STR;
						elseif(is_null($val)) $bmode=PDO::PARAM_NULL;
						else 	$bmode=PDO::PARAM_STR;
					}
					if($is_bind_param){
					  if($bmode===PDO::PARAM_STR){
					  	$len=strlen($val);
					  	if(is_string($key)) $this->sth->bindParam($key, $val, $bmode,$len);
					  	else $this->sth->bindParam($key+1, $val, $bmode,$len);
					  }else{
					  	if(is_string($key)) $this->sth->bindParam($key, $val, $bmode);
					  	else $this->sth->bindParam($key+1, $val, $bmode);
					  }
					}else{
					  if(is_string($key)) $this->sth->bindValue($key, $val, $bmode);
					  else $this->sth->bindValue($key+1, $val, $bmode);
					}
				}
			}
			return $this->sth->execute();
		} catch (Exception $e) {
			$err="[{$e->getCode()}],{$e->getMessage()}";
			if(isset($GLOBALS['context'])){
				$err=lang('db_err_execute').$err;
				$GLOBALS['context']->log_error($err.','.$this->last_query);
				throw new Exception($err);
			}else	throw new Exception($err);
		}
	}
	/**
	 * 运行sql语句
	 * @param string $sql sql语句，建议采用占位符或?，防止sql注入，提高执行速度
	 * @param array $values 占位符或?对应的值
	 * @param boolean|NULL $ro_hint readonly_hint sql语句只读提示，NULL：忽略此提示，分析sql语句是否查询，true：只读，false：可写。
	 */
	function query($sql,$values=array(),$ro_hint=NULL){
		$this->prepare($sql,$ro_hint);
		return $this->execute($values);
	}
	/**end handle origin db sql**/

	/**
	 * 得到in子句和对应值的数组，仅将对应值添加到$in_values。
	 * <br>例：$s=$db->get_in_sql('order_id','12232,121',$vals);或$s=$db->get_in_sql('order_id',array(12232,121),$vals);
	 * $s 返回 in子句”:order_id1,:order_id2“  $vals返回 数组”:order_id1=>12232,:order_id2=>121“
	 * @param string $col				字段名称
	 * @param array|string $col_value   字段值 ,强烈建议为数组，包括数组和字符串，
	 * <br>字符串形式：数值型12,343,56，字符型 '12',"343",'56 ，字符型的项中不得包含英文逗号,
	 * @param array $in_values   返回值数组
	 * @param boolean $bind_name   是采用名称绑定还是?绑定，默认名称绑定，:col or ?
	 * @return string 	        返回in子句
	 */
	function get_in_sql($col,$col_value,array & $in_values,$bind_name=true){
		if(is_string($col_value) && strpos($col_value,',')!==false){
			$vals=explode(',',$col_value);
			$col_value=array();
			foreach ($vals as $v){
				$v=trim($v);
				if(strpos($v,'"')===false && strpos($v,"'")===false)
					$col_value[]=(int) $v;
				else
					$col_value[] = trim($v,'\'"');
			}
		}else if(!is_array($col_value)){
			if(is_object($col_value)) $col_value=get_object_vars($col_value);
			else $col_value=array($col_value);
		}
		$idx=1;
		$in_sql='';
		foreach ($col_value as $val){
			if($bind_name){
				$n=':' . $col .$idx;
				if($idx ++ ==1) 	$in_sql =  $n ;
				else $in_sql .=  ','. $n ;
				$in_values [$n] = $val;
			}else{
				if($idx ++ ==1) $in_sql = '?';
				else $in_sql .= ',?';
				$in_values[] = $val;
			}
		}
		return $in_sql;
	}

	/**get data from db**/
	private  $row_filter=NULL;
	private  $col_filter=NULL;
	/**
	 * 设置行分块过滤条件子句，如lylx=4
	 * @param string $row_filter 条件子句
	 */
	function set_row_filter($row_filter=NULL){
		$this->row_filter=$row_filter;
		return $this;
	}
	function set_col_filter($col_filter=NULL){
		$this->col_filter=$col_filter;
		return $this;
	}
	function get_row_filter(){
		return $this->row_filter;
	}
	function get_col_filter(){
		return $this->col_filter;
	}
	/*get**方法，查询数据库数据*/
	/**
	 * 返回sql对应的分页记录列表
	 * @param string $sql		要执行的sql语句
	 * @param array $values		要执行的sql对应的值 ，默认为没有。
	 * @param integer   $limit	分页记录条数 ，默认为20，如果此值===false，等同于get_all
	 * @param integer   $offset	偏移量 ，默认为0，即首条
	 * @param boolean $active_filter	是否激活行列分块过滤
	 * @param boolean|NULL $ro_hint readonly_hint sql语句只读提示，NULL：忽略此提示，分析sql语句是否查询，true：只读，false：可写。
	 * @param boolenm $set_memcache     默认false，为读取缓存， true为去读数据库
	 */
	function get_limit($sql,$values=array(),$limit=20, $offset=0,$active_filter=false,$ro_hint=NULL,$set_memcache=false){
		if($active_filter)	$this->append_filter_clause($sql);
		if($limit !== false)	$this->adapter->limit($sql,$limit,$offset);
		if($values === NULL) $values=array();
		if($this->next_no_execute){
			 $this->query($sql,$values,$ro_hint);
			 return true;
		}
		if(!$set_memcache) {
			//根据参数执行
			if(!empty($values)) {
				$md5_sql = md5($sql.implode('-', $values));
			}else {
				$md5_sql = md5($sql);
			}
			$data =array();
			$data=$GLOBALS['context']->cache->get('/sqls/'.$md5_sql.'_sql');
			if($data !==NULL) {
				return $data;
			}else {
				if(! $this->query($sql,$values,$ro_hint)) return false;
				if($this->row_key_is<0) {
					$data = $this->sth->fetchAll(PDO::FETCH_NUM);
				}else if($this->row_key_is>0) {
					$data = $this->sth->fetchAll();
				}else {
					$data =  $this->sth->fetchAll(PDO::FETCH_ASSOC);
				}
				$GLOBALS['context']->cache->set('/sqls/'.$md5_sql.'_sql', $data, $this->cache_lifetime, $sql, $values);
				return $data;
			}
		}else {
			if(! $this->query($sql,$values,$ro_hint)) return false;
			if($this->row_key_is<0) return $this->sth->fetchAll(PDO::FETCH_NUM);
			else if($this->row_key_is>0) return $this->sth->fetchAll();
			else	return $this->sth->fetchAll(PDO::FETCH_ASSOC);
		}
	}
	/**
	 * 返回sql对应的记录列表
	 * @param string $sql		要执行的sql语句
	 * @param array $values		要执行的sql对应的值 ，默认为没有。
	 * @param boolean $active_filter	是否激活行列分块过滤
	 * @param boolean|NULL $ro_hint readonly_hint sql语句只读提示，NULL：忽略此提示，分析sql语句是否查询，true：只读，false：可写。
	 */
	function get_all($sql,$values=array(),$active_filter=false,$ro_hint=NULL,$set_memcache=false){
		return $this->get_limit($sql,$values,false,false,$active_filter,$ro_hint,$set_memcache);
	}
	/**
	 * 返回sql对应的分页首列结果值列表
	 * @param string $sql		要执行的sql语句
	 * @param array $values		要执行的sql对应的值 ，默认为没有。
	 * @param integer   $limit	分页记录条数 ，默认为20，如果此值===false，等同于get_all
	 * @param integer   $offset	页数 ，默认为0，即首页
	 * @param boolean $active_filter	是否激活行列分块过滤
	 * @param boolean|NULL $ro_hint readonly_hint sql语句只读提示，NULL：忽略此提示，分析sql语句是否查询，true：只读，false：可写。
	 */
	function get_col($sql,$values=array(),$limit=20, $offset=0,$active_filter=false,$ro_hint=NULL,$set_memcache=false){
		$row_key_is=$this->row_key_is;
		$this->row_key_is=self::KEY_DIGIT;
		$data=$this->get_limit($sql,$values,$limit,$offset,$active_filter,$ro_hint,$set_memcache);
		$this->row_key_is=$row_key_is;
		$result=array();
		foreach($data as $row)	$result[]=$row[0];
		return $result;
	}
	/**
	 * 返回sql对应的全部首列结果值列表
	 * @param string $sql		要执行的sql语句
	 * @param array $values		要执行的sql对应的值 ，默认为没有。
	 * @param boolean $active_filter	是否激活行列分块过滤
	 * @param boolean|NULL $ro_hint readonly_hint sql语句只读提示，NULL：忽略此提示，分析sql语句是否查询，true：只读，false：可写。
	 */
	function get_all_col($sql,$values=array(),$active_filter=false,$ro_hint=NULL,$set_memcache=false){
		return $this->get_col($sql,$values,false,false,$active_filter,$ro_hint,$set_memcache);
	}
	/**
	 * 返回sql对应的首行记录
	 * @param string $sql		要执行的sql语句
	 * @param array $values		要执行的sql对应的值 ，默认为没有。
	 * @param boolean $active_filter	是否激活行列分块过滤
	 * @param boolean|NULL $ro_hint readonly_hint sql语句只读提示，NULL：忽略此提示，分析sql语句是否查询，true：只读，false：可写。
	 */
	function get_row($sql,$values=array(),$active_filter=false,$ro_hint=NULL,$set_memcache=false){
		$result=$this->get_limit($sql,$values,1,0,$active_filter,$ro_hint,$set_memcache);
		if($result && count($result)>0)	return $result[0];
		else  return $result;
	}
	/**
	 * 返回sql对应的首行记录，首列的结果值
	 * @param string $sql		要执行的sql语句
	 * @param array $values		要执行的sql对应的值 ，默认为没有。
	 * @param boolean $active_filter	是否激活行列分块过滤
	 * @param boolean|NULL $ro_hint readonly_hint sql语句只读提示，NULL：忽略此提示，分析sql语句是否查询，true：只读，false：可写。
	 */
	function get_value($sql,$values=array(),$active_filter=false,$ro_hint=NULL,$set_memcache=false){
		$row_key_is=$this->row_key_is;
		$this->row_key_is=self::KEY_DIGIT;
		$result=$this->get_row($sql,$values,$active_filter,$ro_hint,$set_memcache);
		$this->row_key_is=$row_key_is;
		if($result && count($result)>0)	return $result[0];
		else  return false;
	}


    /**end get data from db**/

	/**
	 * 得到序列的下一个值，oracle为实际序列，mysql为序列表。
	 * <br>mysql序列表结构:CREATE TABLE $seq (`seq` bigint(20) NOT NULL AUTO_INCREMENT,PRIMARY KEY (`seq`)) ENGINE=InnoDB
	 * @param string $seq_name  序列名称，oracle为实际序列名，mysql为序列表名。
	 */
	function get_seq_next_value($seq_name){
		return $this->adapter->seq_next($seq_name);
	}
	/**
	 * 返回当前自动递增字段插入值。
	 * @param string $sequence 自动递增字段对应sequence名称，mysql不需要传，oracle等必需
	 */
    function insert_id($sequence=null){
    	if(! $this->pdo) $this->get_pdo(false);
    	return $this->adapter->insert_id($sequence);
    }
    /**
     * 返回sql语句执行影响记录行数
     */
    function affected_rows(){
    	if(! $this->pdo || !$this->sth) return -1;
    	return $this->sth->rowCount();
    }

	function append_limit_clause(&$sql, $limit=20, $offset=0){
	 	$this->adapter->limit($sql,$limit,$offset);
	}
	function append_filter_clause(&$sql,$filter_col=true){
		if(! $this->col_filter && ! $this->row_filter) return;

		$sql_add='SELECT ';
		if($this->col_filter && $filter_col){
			if(is_array($this->col_filter)) $sql_add .= implode(',',$this->col_filter);
			else $sql_add .=$this->col_filter;
		}else $sql_add .='*';
		$sql_add .=' FROM (';
		$sql =$sql_add . $sql .') __fa';
		if($this->row_filter ){
			$sql .=' WHERE ' .$this->row_filter;
		}
	}

	/***transcation***/
	private $trans_count=0;		//nested transcation ref counter,when ==0,execute transaction op.
	function begin_trans(){
		$this->get_pdo(false);
		if(! $this->nest_trans && $this->trans_count++ >0) return;
		$this->pdo->beginTransaction();
	}
	function commit(){
		if($this->pdo){
			if(! $this->nest_trans && --$this->trans_count >0) return;
			$this->pdo->commit();
		}
	}
	function rollback(){
		if($this->pdo){
			if(! $this->nest_trans && --$this->trans_count >0) return;
			$this->pdo->rollBack();
		}
	}
	function transaction($closure){
		try{
			$this->trans_begin();
			$param=func_get_args();
			array_shift($param);
			call_user_func_array($closure,$param);
			$this->commit();
		}catch (Exception $e){
			$this->rollback();
			throw $e;
		}
	}
	/***end transcation***/
    /**
     * 为数据库语句安全，对语句进行处理，建议使用预定义语句模式，而不是使用此方法。
     * @param $string
     */
    function escape($string){
    	if(! $this->pdo) $this->get_pdo();
    	return trim($this->pdo->quote($string),"'");
    }
    /**
     * 对数据库表名，列名进行·处理，此方法兼容mysq、oracle。
     * @param string $string 表名或者列名
     * @return string 返回处理后的名称
     */
	function quote($string){
		return $string[0] === $this->QUOTE_CHAR || $string[strlen($string)-1] === $this->QUOTE_CHAR ?
			$string : $this->QUOTE_CHAR . $string . $this->QUOTE_CHAR;
	}
	/**
	 * 返回上次sql和数据的MD5数据摘要
	 */
	function get_last_key(){
		$str=$this->last_query;
		foreach($this->last_values as $key=>$val) $str .=$key.$val;
		return md5($str);
	}
	/**
	 * 得到当前pdo错误参数
	 * @param int $code	错误吗
	 * @param string $msg 错误消息
	 */
	function get_error(& $code,& $msg){
		if(! $this->pdo){
			$code=0;
			$msg='';
			return ;
		}
		$code=$this->pdo->errorCode();
		if($code==NULL && $this->sth){
			$code=$this->sth->errorCode();
			$code=implode(',',$this->sth->errorInfo());
			return;
		}
		$msg=implode(',',$this->pdo->errorInfo());
	}

	/**query col info,will cache the data**/
	/**
	 * 返回表对应字段的数组，系统将缓存查询结果
	 * @param string $table 表名
	 * @return array 字段的列表
	 */
	function query_for_cols($table){
		$cols = NULL;
		$cols_info=$this->query_col_info($table);
	    foreach ($cols_info as $row)  	$cols[]=current($row);
        return 	$cols;
	}
	/**
	 * 返回表对应字段、关键字段的数组，系统将缓存查询结果
	 * @param string $table 表名
	 * @return array 字段的列表
	 */
	function query_for_key_cols($table,array & $cols,array & $pks){
		$this->adapter->get_key_cols($table,$cols,$pks);
	}

	function query_col_info($table){
		$key=$this->col_info_prefix.strtolower($table);
		if(! isset($GLOBALS['context']->cache)) $cache=NULL;
		else	$cache=$GLOBALS['context']->cache;

		if($cache && $this->cache_valid){
			$result=$cache->get($key);
			if($result) return $result;
		}
		$sql= $this->adapter->get_col_info_sql($table);
		$result=$this->get_all($sql,array(),false,true);
		$this->cache_valid=true;

		if($cache && $result) $cache->set($key,$result);	//cache table col info
		return $result;
	}
	/**end query col info**/
	function query_for_tables(){
		$sql=& $this->adapter->get_tables_sql();
		return $this->get_col($sql,array(),false);
	}
	/*register default db to $context*/
	static function register($prop) {
		$context=$GLOBALS['context'];
		$db_type=$context->get_app_conf('db_server'.$prop);
		$db_host=$context->get_app_conf('db_host'.$prop);
		$db_user=$context->get_app_conf('db_user'.$prop);
		$db_pass=$context->get_app_conf('db_pass'.$prop);
		$db_name=$context->get_app_conf('db_name'.$prop);
		$conf=array('type'=>$db_type,'host'=>$db_host,'user'=>$db_user, 'pwd'=>$db_pass, 'name'=>$db_name);
		$db_port=$context->get_app_conf('db_port'.$prop);
		if(isset($db_port))	$conf['port'] = $db_port;
		$db = new PDODB($conf);
		//$GLOBALS ['context']->log_debug ( "pdodb object [type,host,db,user, port] create:{$db_type},{$db_host},{$db_name},{$db_user}, {$db_port}" );
		return $db;
	}

	//以下都是辅助函数

	/**
	 * 创建TableMapper对象,数据库对象为$this.
	 * @param string $table	数据表名称
	 * @return TableMapper  返回$table对应的表映射对象.
	 */
	function create_mapper($table,$alias=NULL){
		if($this->table_mapper_none){
			require_once  dirname(__FILE__).DIRECTORY_SEPARATOR.'TableMapper.class.php';
			$this->table_mapper_none=false;
		}
		return new TableMapper($table,$this,$alias);
	}
	private $table_mapper_none=true;



	/**
	 * @deprecated for old Mysql.class,use TableMapper.class
	 */
    function escape_string($unescaped_string){
    	return $this->escape($unescaped_string);
    }
    function getAll($sql, $key_index=''){
    	return $this->get_all($sql,array());
    }
    function getOne($sql, $limited = false){
    	return $this->get_value($sql,array());
    }
    function getRow($sql, $limited = false){
    	return $this->get_row($sql,array());
    }
	public function trans_rollback(){
		$this->rollback();
	}
	public function trans_commit(){
		$this->commit();
	}
	public function trans_begin(){
		$this->begin_trans();
	}
	function getCol($sql){
		return $this->get_col($sql,array(),true);
	}
	function insert($table_name, $data){
		return $this->create_mapper($table_name)->insert($data);
	}
	function update($table_name, $data, $where='',array $is_func_key=NULL){
		return $this->create_mapper($table_name)->update($data,$where,$is_func_key);
	}
	function autoExecute($table, $field_values, $mode = 'INSERT', $where = '', $querymode = ''){
		$cols_filter=$this->query_for_cols($table);
		if ($mode == 'INSERT') $this->insert($table, $field_values,$cols_filter);
		else $this->update($table, $field_values,$where,$cols_filter);
	}
	function autoReplace($table, $field_values, $update_values, $where = '', $querymode = ''){
		 $this->create_mapper($table)->save($field_values);
	}
	/***end for old Mysql.class***/

}